﻿using System.ComponentModel;
using System.Drawing;
using System;
using System.Collections.Generic;
using System.Data.Objects;
using System.Windows.Forms;
using System.Collections;
using System.Diagnostics;
using System.ComponentModel.Design;
using System.Linq;
using UtfprLibrary.EntityFramework;
using UtfprLibrary.ComponentModel;
using UtfprLibrary.EntityFramework.ComponentModel;
using System.Data.Objects.DataClasses;
using UtfprLibrary.Windows.Forms.Core;
using UtfprLibrary.EntityFramework.Helpers;

namespace UtfprLibrary.Windows.Forms {
    [ToolboxItem(true),
    ToolboxBitmap(typeof(ResourceFinder), "Resources.VSD_UtfprDataSource.png"),
    DefaultProperty("ObjectContextType")]
    public partial class UtfprDataSource : Component, IEntityDataSource, IEntityDataSourceEvents, IEntityDataSourceProvider, IUtfprDesignTimeService {
        #region Constructors

        public UtfprDataSource() {
            InitializeComponent();
        }

        public UtfprDataSource(IContainer container) {
            container.Add(this);
            InitializeComponent();
        }

        #endregion

        #region Methods

        /// <summary>
        /// Creates an IBindingList based on a given query.
        /// </summary>
        /// <param name="query"><see cref="IEnumerable"/> used as a data source for the list.</param>
        /// <returns>An <see cref="IBindingList"/> that provides a sortable/filterable view of the data.</returns>
        public IBindingList CreateView(IEnumerable query) {
            // get the query type
            Type type = typeof(object);
            foreach (var item in query) {
                type = item.GetType();
                break;
            }

            // create the binding list
            var listType = typeof(EntityBindingList<>);
            listType = listType.MakeGenericType(type);
            var list = (IEntityBindingList)Activator.CreateInstance(listType, this, query, type.Name, true);
            return list;
        }

        /// <summary>
        /// Populates the EntitySets collection from the current DomainContext.
        /// </summary>
        private void generateEntitySets(Type ctxType) {
            _entSets.Clear();
            if (ctxType != null) {
                foreach (var pi in ctxType.GetProperties()) {
                    var type = pi.PropertyType;
                    if (type.IsGenericType &&
                        type.GetGenericTypeDefinition() == typeof(ObjectSet<>) &&
                        type.GetGenericArguments().Length == 1) {
                        var objSet = new EntitySet(this, pi);
                        _entSets.Add(objSet);
                    }
                }
            }
        }

        #endregion

        #region IUtfprControlDesignTime

        [Browsable(false)]
        bool IUtfprDesignTimeService.IsInDesignMode {
            get {
                if (Process.GetCurrentProcess().ProcessName == "devenv") {
                    return true;
                } else {
                    return false;
                }
            }
        }

        #endregion

        #region IEntityDataSourceProvider

        bool IListSource.ContainsListCollection {
            get { return true; }
        }
        
        System.Collections.IList IListSource.GetList() {
            //ensures that the object context is created

            var obj = ((IEntityDataSource)this).ObjectContext;

            // This method is completely undocumented in MSDN.
            //
            // Instead of returning a list of the lists available (nice and logical),
            // you should return a list with ONE object, which exposes the lists available 
            // as if they were properties of this ONE object. UGH!
            //
            var list = new List<EntitySetTypeDescriptor>();
            list.Add(new EntitySetTypeDescriptor(this));

            return list;
        }

        IEntityDataSource IEntityDataSourceProvider.EntityDataSource {
            get { return this; }
        }

        private ComponentCollection _bindingSources;
        [Category("Data"), Description("A IComponent Collection representing all BindingSources of the Form.")]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
        public ComponentCollection BindingSources {
            get {
                var host = base.Site.GetService(typeof(IDesignerHost)) as IDesignerHost;
                if (host != null) {
                    var componentHost = host.RootComponent;
                    if (componentHost is ContainerControl) {
                        var bindingSources = (componentHost as ContainerControl)
                            .Container.Components.OfType<BindingSource>();

                        var ic = new IComponent[bindingSources.Count()];
                        int i = 0;
                        foreach (var bs in bindingSources) {
                            ic[i] = bs;
                            i++;
                        }
                        _bindingSources = new ComponentCollection(ic);
                    }
                }

                return _bindingSources;
            }
        }

        #endregion

        #region IComponentChangeService

        private IComponentChangeService _changeService;

        // This override allows the control to register event handlers for IComponentChangeService events
        // at the time the control is sited, which happens only in design mode.
        public override ISite Site {
            get { return base.Site; }
            set {
                // Clear any component change event handlers.
                clearChangeNotifications();

                // Set the new Site value.
                base.Site = value;

                _changeService = (IComponentChangeService)GetService(typeof(IComponentChangeService));

                // Register event handlers for component change events.
                registerChangeNotifications();
            }
        }

        private void clearChangeNotifications() {
            // The m_changeService value is null when not in design mode, 
            // as the IComponentChangeService is only available at design time.	
            _changeService = (IComponentChangeService)GetService(typeof(IComponentChangeService));

            // Clear our the component change events to prepare for re-siting.				
            if (_changeService != null) {
                _changeService.ComponentChanged -= new ComponentChangedEventHandler(onComponentChanged);
                _changeService.ComponentAdded -= new ComponentEventHandler(onComponentAdded);
            }
        }

        private void registerChangeNotifications() {
            // Register the event handlers for the IComponentChangeService events
            if (_changeService != null) {
                _changeService.ComponentChanged += new ComponentChangedEventHandler(onComponentChanged);
                _changeService.ComponentAdded += new ComponentEventHandler(onComponentAdded);
            }
        }

        /* This method handles the OnComponentChanged event to display a notification. */
        private void onComponentChanged(object sender, ComponentChangedEventArgs ce) {
            if (ce.Component is IEntityDataSourceProvider) {
                var host = base.Site.GetService(typeof(IDesignerHost)) as IDesignerHost;
                if (host != null) {
                    var source = new BindingSource();
                    source.DataSource = this;
                    host.Container.Add(source, "utfprBindingSource1");
                    
                    if (host.RootComponent is ContainerControl) {
                        var bindingSources = (host.RootComponent as ContainerControl)
                            .Container.Components.OfType<BindingSource>();

                        foreach (var bs in bindingSources) {
                            if (bs.DataSource != null) {
                                foreach (var es in this._entSets) {
                                    try {
                                        if (es.ElementType == (Type)bs.DataSource) {
                                            bs.DataSource = this;
                                            bs.DataMember = es.Name;
                                        }
                                    } catch { }
                                }
                            }
                        }
                    }
                }
            }

        }

        /* This method handles the OnComponentAdded event to display a notification. */
        private void onComponentAdded(object sender, ComponentEventArgs ce) {
            if (ce.Component is BindingSource) {
                var bs = (BindingSource)ce.Component;
                if (bs.DataSource != null) {
                    foreach (var es in this._entSets) {
                        try {
                            if (es.ElementType == (Type)bs.DataSource) {

                                var host = base.Site.GetService(typeof(IDesignerHost)) as IDesignerHost;
                                if (host != null) {
                                    bs.DataSource = host.Container.Components["utfprBindingSource1"];
                                }

                                //bs.DataSource = this;
                                bs.DataMember = es.Name;
                            }
                        } catch { }
                    }
                }
            } else if (ce.Component is IEntityDataSourceProvider) {
                var host = base.Site.GetService(typeof(IDesignerHost)) as IDesignerHost;
                if (host != null) {
                    if (host.RootComponent is ContainerControl) {
                        var containerControl = host.RootComponent as ContainerControl;
                        if (containerControl.Container.Components.OfType<IEntityDataSourceProvider>().Count() > 1) {
                            throw new ArgumentException("Cannot have multiple datasources.");
                        }

                        var bindingSources = containerControl
                            .Container.Components.OfType<BindingSource>();

                        foreach (var bs in bindingSources) {
                            if (bs.DataSource != null) {
                                foreach (var es in this._entSets) {
                                    try {
                                        if (es.ElementType == (Type)bs.DataSource) {
                                            bs.DataSource = this;
                                            bs.DataMember = es.Name;
                                        }
                                    } catch { }
                                }
                            }
                        }
                    }
                }
            }
        }

        #endregion       

        #region IEntityDataSource

        #region Properties

        private EntitySetCollection _entSets = new EntitySetCollection();
        /// <summary>
        /// Gets the collection of EntitySets available in this UtfprDataSource.
        /// </summary>
        [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        EntitySetCollection IEntityDataSource.EntitySets {
            get { 
                return _entSets; 
            }
        }

        private Type _ctxType; // to support design-time
        /// <summary>
        /// Gets or sets the type of ObjectContext to use as a data source.
        /// </summary>
        /// <remarks>
        /// This property is normally set at design time. Once it is set, the 
        /// component will automatically create an ObjectContext of the appropriate type.
        /// </remarks>
        [Category("Data"), Description("The type of ObjectContext to use as a data source"),
        TypeConverter(typeof(ObjectContextTypeTypeConverter))]
        public Type ObjectContextType {
            get { return _ctxType; }
            set {
                if (value != _ctxType) {
                    // notify
                    OnObjectContextTypeChanging(EventArgs.Empty);

                    // clear existing object context
                    _ctx = null;

                    // set new object context type 
                    _ctxType = value;

                    // generate object sets (will re-create object context if appropriate)
                    generateEntitySets(_ctxType);

                    // notify
                    OnObjectContextTypeChanged(EventArgs.Empty);
                }
            }
        }

        private ObjectContext _ctx;
        /// <summary>
        /// Gets or sets the ObjectContext used as a data source.
        /// </summary>
        [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        ObjectContext IEntityDataSource.ObjectContext {
            get {
                if (_ctx == null && _ctxType != null && !((IUtfprDesignTimeService)this).IsInDesignMode) {
                    try {
                        ((IEntityDataSource)this).ObjectContext =
                            Activator.CreateInstance((Type)_ctxType) as ObjectContext;
                    } catch (Exception e) {
                        MessageBox.Show("Could not create the ObjectContext. ex:" + e.Message);
                    }
                }
                return _ctx;
            }
            set {
                if (_ctx != value) {
                    // notify
                    OnObjectContextChanging(EventArgs.Empty);

                    // save the new context
                    _ctx = value;

                    // update the context type
                    _ctxType = _ctx != null ? _ctx.GetType() : null;

                    // generate object sets
                    generateEntitySets(_ctxType);

                    // notify
                    OnObjectContextChanged(EventArgs.Empty);
                }
            }
        }

        #endregion

        #region Methods

        /// <summary>
        /// Gets a lookup dictionary for a given element type.
        /// </summary>
        /// <param name="elementType">Type of element for which to return a lookup dictionary.</param>
        /// <returns>A lookup dictionary for a given element type.</returns>
        /// <remarks>
        /// <para>The lookup dictionary has keys that correspond to the items on a list and
        /// values that contain a string representation of the items.</para>
        /// <para>When lists of entities are sorted on a column that contains entity references, 
        /// the lookup dictionary is used to provide the sorting order. For example, if you sort a 
        /// list of products by category, the data map associated with the Categories list determines 
        /// the order in which the categories are compared while sorting.</para>
        /// </remarks>
        ListDictionary IEntityDataSource.GetLookupDictionary(Type elementType) {
            foreach (var es in ((IEntityDataSource)this).EntitySets) {
                if (es.ElementType == elementType) {
                    return es.LookupDictionary;
                }
            }
            return null;
        }

        /// <summary>
        /// Saves all changes made to all entity sets back to the database.
        /// </summary>
        int IEntityDataSource.SaveChanges() {
            // notify
            var e = new CancelEventArgs();
            OnSavingChanges(e);

            // save the changes
            int count = 0;
            if (!e.Cancel) {
                try {
                    count = _ctx.SaveChanges();
                    Debug.WriteLine(string.Format("Done. {0} changes saved.", count));

                    // notify
                    OnSavedChanges(e);
                } catch (Exception ex) {
                    OnDataError(new DataErrorEventArgs(ex));
                }
            }

            // done
            return count;
        }

        /// <summary>
        /// Cancels all changes made to all entity sets.
        /// </summary>
        void IEntityDataSource.CancelChanges() {
            // notify
            var e = new CancelEventArgs();
            OnCancelingChanges(e);

            if (_ctx != null && !e.Cancel) {
                try {
                    foreach (var entSet in _entSets) {
                        if (entSet.HasChanged) {
                            entSet.CancelChanges();
                        }
                    }
                    
                    _ctx.AcceptAllChanges();

                    foreach (var entSet in _entSets) {
                        entSet.RefreshList();
                    }
                } catch (Exception ex) {
                    OnDataError(new DataErrorEventArgs(ex));
                }

                // notify
                OnCanceledChanges(EventArgs.Empty);
            }
            Debug.WriteLine("Done. All changes canceled.");
        }

        /// <summary>
        /// Refreshes all views by loading their data from the database.
        /// </summary>
        void IEntityDataSource.Refresh() {
            // notify
            var e = new CancelEventArgs();
            OnRefreshing(e);

            if (!e.Cancel) {
                try {
                    foreach (var entSet in _entSets) {
                        entSet.RefreshView();
                    }
                } catch (Exception ex) {
                    OnDataError(new DataErrorEventArgs(ex));
                }

                // notify
                OnRefreshed(EventArgs.Empty);
                Debug.WriteLine("Done. All views refreshed.");
            }
        }

        void IEntityDataSource.Add(object entity) {
            // notify
            var e = new CancelEventArgs();
            //OnRefreshing(e);

            if (!e.Cancel) {
                try {
                    _ctx.AddObject(_ctx.GetEntitySetName(entity.GetType().Name), entity);
                } catch (Exception ex) {
                    OnDataError(new DataErrorEventArgs(ex));
                }


                // notify
                //OnRefreshed(EventArgs.Empty);
                //Debug.WriteLine("Done. All views refreshed.");
            }
        }

        void IEntityDataSource.Add(IList entities, System.Data.EntityState entityState) {
            // notify
            var e = new CancelEventArgs();
            //OnRefreshing(e);

            if (!e.Cancel) {
                try {
                    foreach (var entity in entities) {
                        //EntityState.Detached
                        if (ObjectContextHelper.GetEntityState(entity) == entityState) {
                            ((IEntityDataSource)this).Add(entity);
                        }
                    }
                } catch (Exception ex) {
                    OnDataError(new DataErrorEventArgs(ex));
                }


                // notify
                //OnRefreshed(EventArgs.Empty);
                //Debug.WriteLine("Done. All views refreshed.");
            }
        }

        void IEntityDataSource.Delete(object entity) {
            // notify
            var e = new CancelEventArgs();
            //OnRefreshing(e);

            if (!e.Cancel) {
                try {
                    _ctx.DeleteObject(entity);
                } catch (Exception ex) {
                    OnDataError(new DataErrorEventArgs(ex));
                }

                // notify
                //OnRefreshed(EventArgs.Empty);
                //Debug.WriteLine("Done. All views refreshed.");
            }
        }
          
        object IEntityDataSource.FindById(Type entityType, object id, FetchModeEnum fetchMode, IUtfprRelatedEndDescription[] relatedEndDescriptions) {
            // notify
            var e = new CancelEventArgs();
            //OnRefreshing(e);

            //ObjectQuery<T> ObjectContext.CreateQuery<T>(queryString, parameters[]).Include(path).FirstOrDefault()

            object objectQuery = null;
            object entity = null;
            if (!e.Cancel) {
                bool wasLazyLoadingEnabled = _ctx.ContextOptions.LazyLoadingEnabled;

                if (fetchMode == FetchModeEnum.Lazy) {
                    _ctx.ContextOptions.LazyLoadingEnabled = true;
                } else {
                    _ctx.ContextOptions.LazyLoadingEnabled = false;
                }

                try {

                    objectQuery = ObjectContextHelper.InvokeCreateQuery(_ctx, entityType, ObjectContextHelper.GetParameters(_ctx, id, entityType));

                    //INCLUDE
                    //if (fetchMode == FetchModeEnum.Eager) {
                    //    foreach (var property in entityType.GetProperties()) {
                    //        if (ObjectContextHelper.IsRelatedEnd(property)) {
                    //            objectQuery = ObjectContextHelper.InvokeInclude(objectQuery, property.Name);
                    //        }
                    //    }
                    //}

                    entity = ObjectContextHelper.InvokeFirstOrDefault(objectQuery, entityType);

                    //LOAD
                    if (fetchMode == FetchModeEnum.Eager) {
                        //foreach (var property in entityType.GetProperties()) {
                        //    if (ObjectContextHelper.IsRelatedEnd(property)) {
                        //        ((IRelatedEnd)property.GetValue(entity, null)).Load();
                        //    }
                        //}

                        if (relatedEndDescriptions != null) {
                            foreach (var description in relatedEndDescriptions) {
                                if (description.IsEagerLoaded) {
                                    ((IRelatedEnd)entityType
                                        .GetProperty(description.RelatedEndName)
                                        .GetValue(entity, null)).Load();
                                }
                            }
                        }
                    }
                    _ctx.ContextOptions.LazyLoadingEnabled = wasLazyLoadingEnabled;
                } catch (Exception ex) {
                    _ctx.ContextOptions.LazyLoadingEnabled = wasLazyLoadingEnabled;
                    OnDataError(new DataErrorEventArgs(ex));
                }

                // notify
                //OnRefreshed(EventArgs.Empty);
                //Debug.WriteLine("Done. All views refreshed.");
            }

            return entity;
        }

        object IEntityDataSource.FindById(Type entityType, object id, bool isString, bool isCaseInsensitive, FetchModeEnum fetchMode, IUtfprRelatedEndDescription[] relatedEndDescriptions) {
            // notify
            var e = new CancelEventArgs();
            //OnRefreshing(e);

            //ObjectQuery<T> ObjectContext.CreateQuery<T>(queryString, parameters[]).Include(path).FirstOrDefault()
                        
            object objectQuery = null;
            object entity = null;
            if (!e.Cancel) {
                if (fetchMode == FetchModeEnum.Lazy) {
                    _ctx.ContextOptions.LazyLoadingEnabled = true;
                } else {
                    _ctx.ContextOptions.LazyLoadingEnabled = false;
                }

                try {

                    objectQuery = ObjectContextHelper.InvokeCreateQuery(_ctx, entityType, ObjectContextHelper.GetParameters(_ctx, id, isString, isCaseInsensitive, entityType));

                    //INCLUDE
                    //if (fetchMode == FetchModeEnum.Eager) {
                    //    foreach (var property in entityType.GetProperties()) {
                    //        if (ObjectContextHelper.IsRelatedEnd(property)) {
                    //            objectQuery = ObjectContextHelper.InvokeInclude(objectQuery, property.Name);
                    //        }
                    //    }
                    //}

                    entity = ObjectContextHelper.InvokeFirstOrDefault(objectQuery, entityType);

                    //LOAD
                    if (entity != null && fetchMode == FetchModeEnum.Eager) {
                        //foreach (var property in entityType.GetProperties()) {
                        //    if (ObjectContextHelper.IsRelatedEnd(property)) {
                        //        ((IRelatedEnd)property.GetValue(entity, null)).Load();
                        //    }
                        //}

                        if (relatedEndDescriptions != null) {
                            foreach (var description in relatedEndDescriptions) {
                                if (description != null) {
                                    if (description.IsEagerLoaded) {
                                        ((IRelatedEnd)entityType
                                            .GetProperty(description.RelatedEndName)
                                            .GetValue(entity, null)).Load();
                                    }
                                }
                            }
                        }
                    }
                } catch (Exception ex) {
                    OnDataError(new DataErrorEventArgs(ex));
                }
                
                // notify
                //OnRefreshed(EventArgs.Empty);
                //Debug.WriteLine("Done. All views refreshed.");
            }
                       
            return entity;
        }

        void IEntityDataSource.LoadRelatedEnds(object entity, IUtfprRelatedEndDescription[] relatedEndDescriptions) {
            //LOAD
            if (entity != null) {
                if (relatedEndDescriptions != null) {
                    foreach (var description in relatedEndDescriptions) {
                        if (description != null) {
                            if (description.IsEagerLoaded) {
                                ((IRelatedEnd)entity.GetType()
                                    .GetProperty(description.RelatedEndName)
                                    .GetValue(entity, null)).Load();
                            }
                        }
                    }
                }
            }
        }

        #endregion

        #endregion

        #region IEntityDataSourceEvents

        /// <summary>
        /// Occurs before the value of the <see cref="ObjectContextType"/> property changes.
        /// </summary>
        [Category("UTFPR"), Description("Occurs before the value of the ObjectContextType property changes.")]
        public event EventHandler ObjectContextTypeChanging;

        /// <summary>
        /// Raises the <see cref="ObjectContextTypeChanging"/> event.
        /// </summary>
        /// <param name="e"><see cref="EventArgs"/> that contains the event parameters.</param>
        protected virtual void OnObjectContextTypeChanging(EventArgs e) {
            if (ObjectContextTypeChanging != null) {
                ObjectContextTypeChanging(this, e);
            }
        }

        /// <summary>
        /// Occurs after the value of the <see cref="ObjectContextType"/> property changes.
        /// </summary>
        [Category("UTFPR"), Description("Occurs after the value of the ObjectContextType property changes.")]
        public event EventHandler ObjectContextTypeChanged;

        /// <summary>
        /// Raises the <see cref="ObjectContextTypeChanged"/> event.
        /// </summary>
        /// <param name="e"><see cref="EventArgs"/> that contains the event parameters.</param>
        protected virtual void OnObjectContextTypeChanged(EventArgs e) {
            if (ObjectContextTypeChanged != null) {
                ObjectContextTypeChanged(this, e);
            }
        }

        /// <summary>
        /// Occurs before the value of the <see cref="ObjectContext"/> property changes.
        /// </summary>
        [Category("UTFPR"), Description("Occurs before the value of the ObjectContext property changes.")]
        public event EventHandler ObjectContextChanging;

        /// <summary>
        /// Raises the <see cref="ObjectContextChanging"/> event.
        /// </summary>
        /// <param name="e"><see cref="EventArgs"/> that contains the event parameters.</param>
        protected virtual void OnObjectContextChanging(EventArgs e) {
            if (ObjectContextChanging != null)
                ObjectContextChanging(this, e);
        }

        /// <summary>
        /// Occurs after the value of the <see cref="ObjectContext"/> property changes.
        /// </summary>
        [Category("UTFPR"), Description("Occurs after the value of the ObjectContext property changes.")]
        public event EventHandler ObjectContextChanged;

        /// <summary>
        /// Raises the <see cref="ObjectContextChanged"/> event.
        /// </summary>
        /// <param name="e"><see cref="EventArgs"/> that contains the event parameters.</param>
        protected virtual void OnObjectContextChanged(EventArgs e) {
            if (ObjectContextChanged != null)
                ObjectContextChanged(this, e);
        }

        /// <summary>
        /// Occurs before the value of the <see cref="DataSource"/> property changes.
        /// </summary>
        [Category("UTFPR"), Description("Occurs before the value of the DataSource property changes.")]
        public event EventHandler DataSourceChanging;

        /// <summary>
        /// Raises the <see cref="DataSourceChanging"/> event.
        /// </summary>
        /// <param name="e"><see cref="EventArgs"/> that contains the event parameters.</param>
        protected virtual void OnDataSourceChanging(EventArgs e) {
            if (DataSourceChanging != null) {
                DataSourceChanging(this, e);
            }
        }

        /// <summary>
        /// Occurs after the value of the <see cref="DataSource"/> property changes.
        /// </summary>
        [Category("UTFPR"), Description("Occurs after the value of the DataSource property changes.")]
        public event EventHandler DataSourceChanged;

        /// <summary>
        /// Raises the <see cref="DataSourceChanged"/> event.
        /// </summary>
        /// <param name="e"><see cref="EventArgs"/> that contains the event parameters.</param>
        protected virtual void OnDataSourceChanged(EventArgs e) {
            if (DataSourceChanged != null) {
                DataSourceChanged(this, e);
            }
        }

        /// <summary>
        /// Occurs before changes are saved to the database.
        /// </summary>
        [Category("UTFPR"), Description("Occurs before changes are saved to the database.")]
        public event CancelEventHandler SavingChanges;

        /// <summary>
        /// Raises the <see cref="SavingChanges"/> event.
        /// </summary>
        /// <param name="e"><see cref="CancelEventArgs"/> that contains the event parameters.</param>
        protected virtual void OnSavingChanges(CancelEventArgs e) {
            if (SavingChanges != null)
                SavingChanges(this, e);
        }

        /// <summary>
        /// Occurs after changes are saved to the database.
        /// </summary>
        [Category("UTFPR"), Description("Occurs after changes are saved to the database.")]
        public event EventHandler SavedChanges;

        /// <summary>
        /// Raises the <see cref="SavedChanges"/> event.
        /// </summary>
        /// <param name="e"><see cref="EventArgs"/> that contains the event parameters.</param>
        protected virtual void OnSavedChanges(EventArgs e) {
            if (SavedChanges != null)
                SavedChanges(this, e);
        }

        /// <summary>
        /// Occurs before changes are canceled and values are reloaded from the database.
        /// </summary>
        [Category("UTFPR"), Description("Occurs before changes are canceled and values are reloaded from the database.")]
        public event CancelEventHandler CancelingChanges;

        /// <summary>
        /// Raises the <see cref="CancelingChanges"/> event.
        /// </summary>
        /// <param name="e"><see cref="CancelEventArgs"/> that contains the event parameters.</param>
        protected virtual void OnCancelingChanges(CancelEventArgs e) {
            if (CancelingChanges != null)
                CancelingChanges(this, e);
        }

        /// <summary>
        /// Occurs after changes are canceled and values are reloaded from the database.
        /// </summary>
        [Category("UTFPR"), Description("Occurs after changes are canceled and values are reloaded from the database.")]
        public event EventHandler CanceledChanges;

        /// <summary>
        /// Raises the <see cref="CanceledChanges"/> event.
        /// </summary>
        /// <param name="e"><see cref="EventArgs"/> that contains the event parameters.</param>
        protected virtual void OnCanceledChanges(EventArgs e) {
            if (CanceledChanges != null)
                CanceledChanges(this, e);
        }

        /// <summary>
        /// Occurs before values are refreshed from the database.
        /// </summary>
        [Category("UTFPR"), Description("Occurs before values are refreshed from the database.")]
        public event CancelEventHandler Refreshing;

        /// <summary>
        /// Raises the <see cref="Refreshing"/> event.
        /// </summary>
        /// <param name="e"><see cref="CancelEventArgs"/> that contains the event parameters.</param>
        protected virtual void OnRefreshing(CancelEventArgs e) {
            if (Refreshing != null)
                Refreshing(this, e);
        }

        /// <summary>
        /// Occurs after values are refreshed from the database.
        /// </summary>
        [Category("UTFPR"), Description("Occurs after values are refreshed from the database.")]
        public event EventHandler Refreshed;

        /// <summary>
        /// Raises the <see cref="Refreshed"/> event.
        /// </summary>
        /// <param name="e"><see cref="EventArgs"/> that contains the event parameters.</param>
        protected virtual void OnRefreshed(EventArgs e) {
            if (Refreshed != null)
                Refreshed(this, e);
        }

        /// <summary>
        /// Occurs when an error is detected while loading data from or saving data to the database.
        /// </summary>
        [Category("UTFPR"), Description("Occurs when an error is detected while loading data from or saving data to the database.")]
        public event EventHandler<DataErrorEventArgs> DataError;

        /// <summary>
        /// Raises the <see cref="DataError"/> event.
        /// </summary>
        /// <param name="e"><see cref="DataErrorEventArgs"/> that contains the event parameters.</param>
        protected virtual void OnDataError(DataErrorEventArgs e) {
            if (DataError != null) {
                DataError(this, e);
            }

            if (!e.Handled) {
                throw e.Exception;
            }
        }

        public event CancelEventHandler Adding;
                
        protected virtual void OnAdding(CancelEventArgs e) {
            if (Adding != null)
                Adding(this, e);
        }

        public event EventHandler Added;

        protected virtual void OnAdded(EventArgs e) {
            if (Added != null)
                Added(this, e);
        }
        
        public event CancelEventHandler Deleting;

        protected virtual void OnDeleting(CancelEventArgs e) {
            if (Deleting != null)
                Deleting(this, e);
        }

        public event EventHandler Deleted;

        protected virtual void OnDeleted(EventArgs e) {
            if (Deleted != null)
                Deleted(this, e);
        }
        
        public event CancelEventHandler FindingById;

        protected virtual void OnFindingById(CancelEventArgs e) {
            if (FindingById != null)
                FindingById(this, e);
        }

        public event EventHandler FoundById;

        protected virtual void OnFoundById(EventArgs e) {
            if (FoundById != null)
                FoundById(this, e);
        }

        #endregion
    }
}
